package problem;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.alibaba.excel.EasyExcel;

import entity.DeviceType;
import entity.DeviceTypeExcel;
import entity.FourTuple;
import entity.MLSIAPSetting;
import entity.Ndh;
import entity.Passenger;
import entity.ThreatExcel;
import listener.DeviceTypeExcelListener;
import listener.MLSIAPListener;
import listener.NdhListener;
import listener.PassengerListener;
import listener.ThreatExcelListener;
import solution.Solution;
import util.FileUtil;

/**
 * 多级安检设备部署
 * 
 * 注意： 乘客和设备在模型中是从1开始编号，在数组中从0开始编号的 方案：将乘客P0和设备D0为空。
 * 
 * @author Dehuai Liu
 */
/*
 * 
 */
/**
 * @author Dehuai Liu
 *
 */
public class MLSIAP extends CombinatorialProblem<Integer, FourTuple<Double, Double, Integer, Double>, Double> {

	private static MLSIAP instance;
	/* SIAP需要补充的属性 */
	int instanceId;// 实例编号
	private final int N1;// 普通乘客的数量
	private final int N2;// 风险乘客的数量
	private final int M1;// 强制检查设备类型的数量
	private final int M2;// 特殊检查设备类型的数量
	private final int K;// 危险物品种类
	private final double T;// 时间上限
	private final int S;// 检查人员人数上限
	private final double omega;// 风险系数
	private final double delta;// P(FC)上界
	private final int NP;// 该问题实例对应的种群大小
	private final double[] betaTks;// P(Tk|T),(K)

	private final int[][] Gjh;// 保存乘客i所在分组分配的设备，如果设备h分配给i，则Gjh=1;否则为0,((2^M2)*(M+1))
	private List<Passenger> PassList;// 乘客集合,(N)
	private List<DeviceType> DevList;// 设备类型集合,(M)

	private final int N;
	private final int M;

	private MLSIAP(int instanceId, boolean isMax, String problemName, int dimension, Integer upper, Integer lower,
			int NFE, int n1, int n2, int m1, int m2, int k, double[] betaTks, double T, int S, double omega,
			double delta, int np) {
		super(isMax, problemName, dimension, upper, lower, NFE);
		/* 需要从外界输入的参数 */
		this.instanceId = instanceId;
		N1 = n1;
		N2 = n2;
		M1 = m1;
		M2 = m2;
		K = k;
		this.betaTks = betaTks;
		this.T = T;
		this.S = S;
		this.omega = omega;
		this.delta = delta;
		this.NP = np;

		/* 中间变量 */
		N = N1 + N2;
		M = M1 + M2;

		/* 算法内部计算产生的变量 */
		Gjh = new int[(int) Math.pow(2, M2)][M + 1];
		DevList = new ArrayList<DeviceType>();
		// 初始化上述算法内部产生的变量
		initialize();
	}

	/**
	 * 返回单例
	 * 
	 * @return
	 */
	public static MLSIAP getInstance(int instanceNo) {
		if (instance == null || instance.instanceId != instanceNo) {
			synchronized (MLSIAP.class) {
				if (instance == null || instance.instanceId != instanceNo) {
					String MLSIAPPath = FileUtil.getFilePath() + FileUtil.MLSIAPPath;
					String DevicePath = FileUtil.getFilePath() + FileUtil.EightDeviceFourThreatPath;
					String PassengerPath = FileUtil.getFilePath() + FileUtil.PassengerPrefix + instanceNo
							+ FileUtil.Passengersuffix;
					String ThreatPath = FileUtil.getFilePath() + FileUtil.FourThreatPath;
					String NdhPath = FileUtil.getFilePath() + FileUtil.NumberOfDevice;

					MLSIAPListener listener1 = new MLSIAPListener();
					DeviceTypeExcelListener listener2 = new DeviceTypeExcelListener();
					PassengerListener listener3 = new PassengerListener();
					ThreatExcelListener listener4 = new ThreatExcelListener();
					NdhListener listener5 = new NdhListener();

					EasyExcel.read(MLSIAPPath, MLSIAPSetting.class, listener1).sheet().doRead();
					EasyExcel.read(DevicePath, DeviceTypeExcel.class, listener2).sheet().doRead();
					EasyExcel.read(PassengerPath, Passenger.class, listener3).sheet().doRead();
					EasyExcel.read(ThreatPath, ThreatExcel.class, listener4).sheet().doRead();
					EasyExcel.read(NdhPath, Ndh.class, listener5).sheet().doRead();

					List<MLSIAPSetting> list1 = listener1.list;
					List<DeviceTypeExcel> list2 = listener2.list;
					List<Passenger> list3 = listener3.list;
					List<ThreatExcel> list4 = listener4.list;
					List<Ndh> list5 = listener5.list;

					for (Passenger Pi : list3) {
						// 创建thi
						double[] thi = new double[list2.size() + 1];
						Pi.setThi(thi);
					}

					ThreatExcel threat = list4.get(0);
					double[] betaTks = { threat.getT1(), threat.getT2(), threat.getT3(), threat.getT4() };

					// 获得实例
					MLSIAPSetting ins = list1.get(instanceNo - 1);
					instance = new MLSIAP(ins.getInstanceNo(), false, "MLSIAP",
							ins.getN1() + ins.getN2() + ins.getM1() + ins.getM2(), (int) (Math.pow(2, ins.getM2()) - 1),
							0, ins.getNfe(), ins.getN1(), ins.getN2(), ins.getM1(), ins.getM2(), ins.getK(), betaTks,
							ins.getT(), ins.getS(), ins.getOmega(), ins.getDelta(), ins.getNp());
					for (DeviceTypeExcel dev : list2) {
						double[] phTk = { dev.getPhT1(), dev.getPhT2(), dev.getPhT3(), dev.getPhT4() };
						DeviceType device = new DeviceType(dev.getH(), dev.getnDh(), phTk, dev.getQh(), dev.getnDh(),
								dev.getTh());
						instance.DevList.add(device);
					}

					// 设置每个设备的Ndh
					Ndh ndh = list5.get(instanceNo - 1);
					int[] Ndhs = { ndh.getnD1(), ndh.getnD2(), ndh.getnD3(), ndh.getnD4(), ndh.getnD5(), ndh.getnD6(),
							ndh.getnD7(), ndh.getnD8() };
					for (int i = 0; i < Ndhs.length; i++) {
						instance.DevList.get(i).setNDh(Ndhs[i]);
					}
					instance.PassList = list3;
				}
			}

		}
		return instance;
	}

	/**
	 * 初始化
	 * 
	 * @param gih2
	 */
	public void initialize() {

		// 初始化Gjh数组
		for (int j = 0; j < Gjh.length; j++) {
			for (int h = 1; h <= M1; h++) {
				Gjh[j][h] = 1;
			}
			int temp = j;
			// 使用除2取余法
			for (int h = M; h > M1; h--) {
				Gjh[j][h] = temp % 2;
				temp /= 2;
				if (temp == 0) {
					break;
				}
			}
		}

	}

	/**
	 * 计算每台设备检查乘客的总时间
	 * 
	 * @param content
	 * @return
	 */
	public DeviceType[] calTotolTimeOfEveryDevice(Integer[] content) {
		double[] timesOfEveryDh = new double[M];
		DeviceType[] devices = new DeviceType[M];
		// 统计每个设备类型下，分配的乘客数量
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (Gjh[content[i]][j + 1] == 1) {
					timesOfEveryDh[j] += 1;
				}
			}
		}

		// 使用插入排序，降序排列每个设备检查乘客的总时间
		for (int h = 0; h < M; h++) {
			DeviceType dev = DevList.get(h);
			// 单台设备检查乘客的总时间
			timesOfEveryDh[h] = timesOfEveryDh[h] / dev.getNDh() * dev.getConstTh();
			dev.setTimeOfInspectPassengers(timesOfEveryDh[h]);
			devices[h] = dev;
			for (int j = h; j > 0; j--) {
				if (devices[j].getTimeOfInspectPassengers() > devices[j - 1].getTimeOfInspectPassengers()) {
					DeviceType temp = devices[j];
					devices[j] = devices[j - 1];
					devices[j - 1] = temp;
				}
			}

		}
		return devices;

	}

	/**
	 * 计算每个乘客在每个设备上检查完的时间
	 */
	public void calTimeOfAllPassengersInAllDevices() {
		for (int h = 1; h <= M; h++) {
			calculateTimeOfDeviceH(h);
		}
	}

	/**
	 * 计算乘客在设备类型h处检查完的时间
	 * 
	 * @param h
	 */
	public void calculateTimeOfDeviceH(int h) {
		DeviceType Dh = DevList.get(h - 1);
		Passenger[] Qh = new Passenger[N + 1];// Passenger[0]=null的里面没有乘客
		int q = 1;// 用于记录Qh下元素的个数+1
		// 遍历每一个乘客，把分配给设备h的乘客加入到Qh中
		for (Passenger Pi : PassList) {
			// 如果乘客Pi被分配给了设备h
			if (Gjh[Pi.getXi()][h] == 1) {
				Qh[q++] = Pi;
				// 采用插入排序对现有的乘客按到达时间升序排序
				int j;
				for (j = q - 1; j > 1; j--) {
					// 如果当前乘客到达时间更小
					if (Qh[j].getTimeOfDeviceH(h - 1) < Qh[j - 1].getTimeOfDeviceH(h - 1)) {
						// 交换位置
						Passenger temp = Qh[j];
						Qh[j] = Qh[j - 1];
						Qh[j - 1] = temp;
					} else {
						break;
					}
				}
			} else {
				Pi.setTimeOfDeviceH(h, Pi.getTimeOfDeviceH(h - 1));
			}
		}
		// 在Qh中，从头往后计算时间
		for (int i = 1; i < q; i++) {
			Passenger Pq = Qh[i];
			int prehi = i - Dh.getNDh() <= 0 ? 0 : i - Dh.getNDh();
			double tOfh;
			double t_preh = Pq.getTimeOfDeviceH(pre(Pq.getXi(), h));
			if (prehi == 0) {
				tOfh = t_preh + Dh.getTh();
			} else {
				double t_prei = Qh[prehi].getTimeOfDeviceH(h);
				tOfh = max(t_preh, t_prei) + Dh.getTh();
			}
			Pq.setTimeOfDeviceH(h, tOfh);

		}
	}

	/**
	 * 返回设备h在分组Gj中，前一个设备编号；如果它是第一个，返回0
	 * 
	 * @param groupNum
	 * @param h
	 * @return
	 */
	public int pre(int groupNum, int h) {
		// 数组中的编号是从0开始计算的
		if (h == 1 || h == 0) {
			return 0;
		}
		for (int i = h - 1; i >= 1; i--) {
			if (Gjh[groupNum][i] == 1) {
				return i;
			}
		}
		return 0;
	}

	/**
	 * 返回Gj中最后一个设备编号
	 * 
	 * @param groupNum
	 * @return
	 */
	public int last(int groupNum) {
		for (int i = M; i > M1; i--) {
			if (Gjh[groupNum][i] == 1)
				return i;
		}
		// 没有特殊设备的情况
		return M1;
	}

	public double max(double d1, double d2) {
		return d1 > d2 ? d1 : d2;
	}

	@Override
	public FourTuple<Double, Double, Integer, Double> calculate(Solution<Integer, Double> solution) {
		// 得到解中的内容
		Integer[] content = solution.getContent();
		int i;
		// 更新每个乘客的分组
		for (i = 0; i < N; i++) {
			PassList.get(i).setXi(content[i]);
		}
		// 更新每个设备的分配检查人员
		for (int h = 0; h < DevList.size(); h++) {
			DeviceType Dh = DevList.get(h);
			Dh.setSh(content[i++]);
			// 更新th
			Dh.setTh(calTh(Dh.getConstTh(), Dh.getNDh(), Dh.getSh()));
		}
		// 更新所有乘客，在所有的设备上检查完的时间
		calTimeOfAllPassengersInAllDevices();

		// 计算目标函数值
		double objectiveFunction = calPFA(1, N1) + omega * calPFC(N1 + 1, N);
		// 计算约束条件值
		double valueOfPFC = calPFC(1, N);
		int totalS = calTotalS();
		double maxTime = calMaxTime();
		// 罚函数值
		final int C1 = 1000000, C2 = 1, C3 = 1;// 惩罚系数
		double d1 = valueOfPFC - delta <= 0 ? 0 : valueOfPFC - delta;
		int d2 = totalS - S <= 0 ? 0 : totalS - S;
		double d3 = maxTime - T <= 0 ? 0 : maxTime - T;
		double penaltyFunction = d1 * C1 + d2 * C2 + d3 * C3;

		return new FourTuple<Double, Double, Integer, Double>(objectiveFunction + penaltyFunction, valueOfPFC, totalS,
				maxTime);
	}

	/**
	 * 计算P(FA)
	 * 
	 * @return
	 */
	public double calPFA(int minI, int maxI) {
		double result = 0.0;
		for (int i = minI; i <= maxI; i++) {
			Passenger Pi = PassList.get(i - 1);
			double temp1 = 1 - Pi.getPiT();
			double temp2 = 1.0;
			for (int h = 0; h < M; h++) {
				// 如果乘客i被分配到了设备h
				if (Gjh[Pi.getXi()][h + 1] == 1) {
					DeviceType dev = DevList.get(h);
					temp2 = temp2 * (1 - dev.getQh());
				}
			}
			temp2 = 1 - temp2;

			result = result + temp1 * temp2;
		}
		return result / (maxI - minI + 1);
	}

	/**
	 * 计算P(FC)
	 * 
	 * @return
	 */
	public double calPFC(int minI, int maxI) {
		double result = 0.0;
		for (int i = minI; i <= maxI; i++) {
			Passenger Pi = PassList.get(i - 1);
			double temp1 = Pi.getPiT();
			double temp2 = 0.0;
			for (int k = 0; k < betaTks.length; k++) {
				double temp3 = 1.0;
				for (int h = 0; h < M; h++) {
					if (Gjh[Pi.getXi()][h + 1] == 1) {
						DeviceType dev = DevList.get(h);
						double[] phTk = dev.getPhTk();
						temp3 = temp3 * (1 - phTk[k]);
					}

				}
				temp2 = temp2 + temp3 * betaTks[k];
			}
			result = result + temp1 * temp2;
		}
		return result / (maxI - minI + 1);
	}

	/**
	 * 计算当前解的分配的检查人员总数
	 * 
	 * @return
	 */
	public int calTotalS() {
		int result = 0;
		for (DeviceType dev : DevList) {
			result += dev.getSh();
		}
		return result;
	}

	/**
	 * 返回当前解的乘客通过检查花费的最大时间
	 * 
	 * @return
	 */
	public double calMaxTime() {
		double max = 0.0;
		for (Passenger Pi : PassList) {
			double lastTime = Pi.getThi()[M];
			if (lastTime > max) {
				max = lastTime;
			}
		}
		return max;
	}

	/**
	 * 计算th
	 * 
	 * @param Th
	 * @param NDh
	 * @param Sh
	 * @return
	 */
	public double calTh(double Th, int NDh, int Sh) {
		return ((2.0 / 3) * Th * Sh + Th * (NDh - Sh)) / NDh;
	}

	@Override
	public String toString() {
		return "MLSIAP [instanceId=" + instanceId + ", N1=" + N1 + ", N2=" + N2 + ", M1=" + M1 + ", M2=" + M2 + ", K="
				+ K + ", T=" + T + ", S=" + S + ", omega=" + omega + ", delta=" + delta + ", betaTks="
				+ Arrays.toString(betaTks) + ", Gjh=" + Arrays.toString(Gjh) + ", PassList=" + PassList + ", DevList="
				+ DevList + ", N=" + N + ", M=" + M + "]";
	}

	@Override
	public boolean judgeConstraint(Solution<Integer, Double> solution) {
		return false;
	}

	public static void setInstance(MLSIAP instance) {
		MLSIAP.instance = instance;
	}

	public int getInstanceId() {
		return instanceId;
	}

	public void setInstanceId(int instanceId) {
		this.instanceId = instanceId;
	}

	public List<Passenger> getPassList() {
		return PassList;
	}

	public void setPassList(List<Passenger> passList) {
		PassList = passList;
	}

	public List<DeviceType> getDevList() {
		return DevList;
	}

	public void setDevList(List<DeviceType> devList) {
		DevList = devList;
	}

	public int getN1() {
		return N1;
	}

	public int getN2() {
		return N2;
	}

	public int getM1() {
		return M1;
	}

	public int getM2() {
		return M2;
	}

	public int getK() {
		return K;
	}

	public double getT() {
		return T;
	}

	public int getS() {
		return S;
	}

	public double getOmega() {
		return omega;
	}

	public double getDelta() {
		return delta;
	}

	public double[] getBetaTks() {
		return betaTks;
	}

	public int[][] getGjh() {
		return Gjh;
	}

	public int getN() {
		return N;
	}

	public int getM() {
		return M;
	}

	public int getNP() {
		return NP;
	}

}
